# -*- coding: utf-8 -*-
import socket
import select
import errno
import struct
import logging

logger = logging.getLogger(__name__)


class ServerBase(object):
    MAX_HOST_CLIENTS_INDEX = 0xfffe
    NET_CONNECTION_NEW = 0  # new connection
    NET_CONNECTION_LEAVE = 1  # lost connection
    NET_CONNECTION_DATA = 2  # data coming
    NET_HEAD_LENGTH_SIZE = 4  # 4 bytes little endian (x86)
    NET_HEAD_LENGTH_FORMAT = '<I'

    def __init__(self):
        super(ServerBase, self).__init__()
        self.socket = socket.socket()
        self.event = list()
        self.conns = dict()

        self.inputs = [self.socket]
        self.outputs = list()

    def setup(self, ip='localhost', port=2435):
        self.socket.bind((ip, port))
        self.socket.listen(self.MAX_HOST_CLIENTS_INDEX)
        self.socket.setblocking(True)

    def set_blocking(self, is_block):
        self.socket.setblocking(is_block)

    def process(self):
        readable, writable, exceptional = select.select(self.inputs, self.outputs, self.conns.values())
        for conn in readable:
            if conn is self.socket:
                self._handle_conn()
            else:
                self._handle_recv(conn)
        for conn in writable:
            pass
        for conn in exceptional:
            self._handle_leave(conn)

    def _handle_recv(self, conn):
        remain_text = bytes()
        while True:
            data = None
            try:
                data = conn.recv(1024)
                if not data:        # 当收到空数据时，应为socket端开
                    self._handle_leave(conn)
                    return -1
            except socket.error as e:
                if e.errno not in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
                    self._handle_leave(conn)
                    return -1
            if not data:
                break
            remain_text = self._pack_events(remain_text + data, id(conn))

    def _pack_events(self, data, conn_id):
        recv_len = len(data)
        curr_len = 0
        while True:
            if recv_len - curr_len < self.NET_HEAD_LENGTH_SIZE:
                return data[curr_len:]
            data_len = struct.unpack(self.NET_HEAD_LENGTH_FORMAT, data[curr_len: curr_len + self.NET_HEAD_LENGTH_SIZE])[0]
            if recv_len < curr_len + data_len + self.NET_HEAD_LENGTH_SIZE:
                return data[curr_len:]
            curr_len += self.NET_HEAD_LENGTH_SIZE
            self.event.append((self.NET_CONNECTION_DATA, conn_id, data[curr_len:curr_len + data_len]))
            # print(self.NET_CONNECTION_DATA, conn_id, data[curr_len:curr_len + data_len])
            # self.send(conn_id, 'REP: ' + data[curr_len:curr_len + data_len])
            curr_len += data_len

    def _handle_conn(self):
        try:
            conn, addr = self.socket.accept()
            conn.setblocking(False)
            self.inputs.append(conn)
            print(conn, addr)
            self.conns[id(conn)] = conn
        except Exception as e:
            print(e)
            pass

    def _handle_leave(self, conn):
        conn_id = id(conn)
        if conn_id in self.conns:
            conn = self.conns.pop(conn_id)
            try:
                print('leave {}'.format(conn.getpeername()))
                conn.close()
            except Exception as e:
                print(e)

        self.inputs.remove(conn)

    def send(self, conn_id, data):
        conn = self.conns.get(conn_id)
        if conn:
            data = struct.pack(self.NET_HEAD_LENGTH_FORMAT, len(data)) + data
            try:
                conn.send(data)
            except socket.error as e:
                logger.info('{}, conn_id: {} sending data failedd'.format(e, conn_id))
                conn.close()

        else:
            logger.info('conn_id: {} not found'.format(conn_id))

    def close(self):
        for conn_id, conn in self.conns.iteritems():
            conn.close()
        self.socket.close()

    @property
    def has_events(self):
        if self.event:
            return True
        else:
            return False

    def get_next_event(self):
        return self.event.pop(0)

